﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;

using System.Threading;
using DatabaseLayer;
using AudioToVideoConversion;
using YouTubeAccessLayer;

namespace IVRJunctionYouTubeUploadService
{
    public partial class IVRJunctionYouTubeUpload : ServiceBase
    {
        private EventWaitHandle waitHandle;
        private Queue<Tuple<String, String>> audioFileLocationQueue = new Queue<Tuple<String, String>>();
        readonly Object locker = new Object();
        //private int count = 0;
        private Thread consumerThread;

        private String imageToBeUsedInVideo;
        private String outputVideoFileLocation;

        private String developerKey;
        private String accountName;
        private String username;
        private String password;

        private String branchId;
        private String IVRJunctionFolderLocation;

        private String title;
        private String keywords;
        private String description;
        private String location;

        private String sSource = "IVR Junction- YouTube Upload Service";
        private String sLog = "Application";
        
        public IVRJunctionYouTubeUpload()
        {
            InitializeComponent();
            this.ServiceName = "IVR Junction- YouTube Upload Service";

            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);
            
            EventLog.WriteEntry(sSource, "Constructor Started");

        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);
            waitHandle = new AutoResetEvent(false);
            consumerThread = new Thread(ConsumerTask);
            consumerThread.Start();

            EventLog.WriteEntry(sSource, "Consumer thread is started", EventLogEntryType.Information);

            // how to tackle hard shutdown
            // if on start YoutubeUploadStatus is one and YouTubeVideoId is null 
            // then make the value 0
            // if YouTubeVideoId is there
            // then make the value 2

            DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();
            objectDatabaseFunctions.fixInvalidStateOfYouTubeUploadStatus();

            EventLog.WriteEntry(sSource, "Service is started", EventLogEntryType.Information);
        }

        protected override void OnStop()
        {
            base.OnStop();
            //consumerThread.

            // waiting for consummer thread to finish
            while (consumerThread.ThreadState == System.Threading.ThreadState.Running)
            {
                // waiting infinitely for consumer thread to finish the processing.
            }

            // terminate the consumer thread
            consumerThread.Abort();

            // Once the consumer thread is done processing, change the status of unfinished audioFiles from 1 to 0
            // take a lock to make sure that no producer thread is trying to access the database.

            lock (locker)
            {
                while (audioFileLocationQueue.Count > 0)
                {
                    var tuple = audioFileLocationQueue.Peek();
                    String audioFileLocation = tuple.Item1;

                    DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();
                    objectDatabaseFunctions.updateStatusofYouTubeUploadForAudioFile(audioFileLocation, 0, null);
                    objectDatabaseFunctions = null;
                    audioFileLocationQueue.Dequeue();

                }
            }

            // close the wait handler.
            waitHandle.Close();

            EventLog.WriteEntry(sSource, "Service is stopped", EventLogEntryType.Information);
        }

        protected override void OnShutdown()
        {
            base.OnShutdown();
            this.OnStop();
        }

        protected override void OnPause()
        {
            base.OnPause();
        }

        protected override void OnContinue()
        {
            base.OnContinue();
        }

        protected override void OnCustomCommand(int command)
        {
            base.OnCustomCommand(command);

            if (command == 129)
            {
                Thread threadProducer = new Thread(ProducerTask);
                try
                {
                    threadProducer.Start();
                    EventLog.WriteEntry(sSource, "Producer thread started", EventLogEntryType.Information);
                }
                catch (ThreadStateException threadStateException)
                {
                    EventLog.WriteEntry(sSource, "Error starting producer thread:"+ threadStateException.Message, EventLogEntryType.Error);
                }
                catch (OutOfMemoryException outOfMemoryException)
                {
                    EventLog.WriteEntry(sSource, "Error starting producer thread:"+ outOfMemoryException.Message, EventLogEntryType.Error);
                }

            }
        }

        public bool internetConnectionAvailable()
        {
            System.Uri Url = new System.Uri("http://www.google.com");

            // by default returning true
            bool internetConnectionAvailableFlag = true;

            System.Net.WebRequest objectWebRequest = null;
            System.Net.WebResponse objectWebResponse;

            try
            {
                objectWebRequest = System.Net.WebRequest.Create(Url);
            }
            catch (System.NotSupportedException notSupportedException)
            {
                // log exceptions
                EventLog.WriteEntry(sSource, "Internet connection available?:"+notSupportedException.Message, EventLogEntryType.Error);
            }
            catch (System.ArgumentNullException argumentNullException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet connection available?:"+argumentNullException.Message, EventLogEntryType.Error);
            }
            catch (System.Security.SecurityException securityException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet connection available?:"+securityException.Message, EventLogEntryType.Error);
            }

            try
            {
                objectWebResponse = objectWebRequest.GetResponse();
                objectWebResponse.Close();
                internetConnectionAvailableFlag = true;
                EventLog.WriteEntry(sSource, "Internet connection is available", EventLogEntryType.Information);
            }

            catch (System.NotImplementedException notImplementedException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet connection error:"+notImplementedException.Message, EventLogEntryType.Error); 
                internetConnectionAvailableFlag = false;
            }
            catch (Exception generalException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet connection error:"+generalException.Message, EventLogEntryType.Error);
                internetConnectionAvailableFlag = false;
            }
            finally
            {
                objectWebRequest = null;
            }

            return internetConnectionAvailableFlag;
        }

        public void ProducerTask()
        {
            
            lock (locker)
            {

                /* select all audio fileLocations which are not yet uploaded on the YouTube. 
                 * set the UploadedOnYouTube coloumn of the table to 1.
                 * Enqueue the file location into the audioFileLocationqueue.
                 * Signal to the Consumer after that
                */

                DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();
                List<Tuple<String, String>> audioFileLocationList = objectDatabaseFunctions.getAudioFilesNotUploadedOnYouTube();

                if (audioFileLocationList == null)
                {
                    // store the error message in a log
                    EventLog.WriteEntry(sSource, "Producer task in unable to read audio file locations from DB", EventLogEntryType.Error);
                }
                else if (audioFileLocationList.Count != 0)
                {
                    int count = 0;
                    while (count < audioFileLocationList.Count)
                    {
                        var tuple = audioFileLocationList[count];
                        String audioFileLocation = tuple.Item1;
                        String phoneNumber = tuple.Item2;
                        int uploadStatus = 1;

                        // update the UploadedOnYouTube coloumn to 1.
                        // uploadStatus 1 signifies that the audioFile is queued to be uploaded on YouTube.

                        Boolean updateSuccessfulFlag = objectDatabaseFunctions.
                            updateStatusofYouTubeUploadForAudioFile(audioFileLocation, uploadStatus, null);

                        // Enqueue the audioFileLocation if update is successful else skip
                        if (updateSuccessfulFlag)
                        {
                            audioFileLocationQueue.Enqueue(tuple);
                        }

                        count++;

                    }


                    // signal to the Consumer
                    EventLog.WriteEntry(sSource, "Producer task has signaled consumer", EventLogEntryType.Information);
                    waitHandle.Set();
                }
            }


        }

        public void ConsumerTask()
        {
            while (true)
            {
                EventLog.WriteEntry(sSource, "Consumer is executing", EventLogEntryType.Information);
                // Get the settings from the IVRJunction Folder
                DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();
                var configurationData = objectDatabaseFunctions.getIVRJunctionConfiguration();
                // if the configuration data is set and readable then execute the service code
                if (configurationData != null && configurationData.Item1!=null)
                {
                    EventLog.WriteEntry(sSource, "Configuration data is successfully read from the database", EventLogEntryType.Information);

                    branchId = configurationData.Item1;
                    IVRJunctionFolderLocation = configurationData.Item2;

                    imageToBeUsedInVideo = IVRJunctionFolderLocation + "\\backgroundImage.jpg";
                    outputVideoFileLocation = IVRJunctionFolderLocation + "\\VideoFile.wmv";

                    var youTubeConfigurationInformation = configurationData.Item3;
                    developerKey = youTubeConfigurationInformation.Item1;
                    accountName = youTubeConfigurationInformation.Item2;
                    username = youTubeConfigurationInformation.Item3;
                    password = youTubeConfigurationInformation.Item4;

                    var youTubeVideoParametersInformation = configurationData.Item4;
                    title = youTubeVideoParametersInformation.Item1;
                    keywords = youTubeVideoParametersInformation.Item2;
                    description = youTubeVideoParametersInformation.Item3;
                    location = youTubeVideoParametersInformation.Item4;

                    // check if there are files which needs to be uploaded on youtube and if the internet access is there
                    while (audioFileLocationQueue.Count > 0
                        && internetConnectionAvailable())
                    {
                        // get the location of the audio file
                        String audioFileLocation = null;
                        String phoneNumber = null;
                        lock (locker)
                        {
                            var audioTuple = audioFileLocationQueue.Peek();
                            audioFileLocation = audioTuple.Item1;
                            phoneNumber = audioTuple.Item2;
                        }

                        // convert the audio to video
                        //

                        AudioToVideo objectAudioToVideo = new AudioToVideo();
                        bool statusAudioToVideoConversion = objectAudioToVideo.convertAudioFiletoVideoFile(audioFileLocation, imageToBeUsedInVideo, outputVideoFileLocation);

                        objectAudioToVideo = null;

                        if (!statusAudioToVideoConversion)
                        {

                            // status code == 0 implies audio to video conversion failed
                            // make the present audio file last in queue and deal with others

                            // implies tackle other audio
                            if (audioFileLocationQueue.Count > 1)
                            {
                                lock (locker)
                                {
                                    var tempTuple = audioFileLocationQueue.Dequeue();
                                    audioFileLocationQueue.Enqueue(tempTuple);
                                }
                                continue;
                            }
                            // as only one audio is present and that has error in file conversion
                            // lets wait for other audio to come and then will try again 
                            else
                                break;
                        }

                        EventLog.WriteEntry(sSource, "Audio to video conversion is successful", EventLogEntryType.Information);


                        // code to upload the video to the YouTube Channel
                        
                        // variables to create a YouTube Request Object
                        // These values must be stored in the database and must come from database
                        
                        // branch Id should be read from the configuaration table in the database. 
                        
                        YouTubeFunctions objectYouTubeFunctions = new YouTubeFunctions(developerKey, accountName, username, password);

                        // variables to create a video object on youtube

                        //String title = "Video by CGNet Swara";
                        //specify uniqueAudioFile Recognizer somewhere
                        // could be combination of branch, audio file name

                        String[] splittedString = audioFileLocation.Split('\\');

                        String finalKeywords = splittedString[splittedString.Length - 1] + "," + branchId + "," + phoneNumber + "," + keywords;
                     
                        int statusCode = objectYouTubeFunctions.UploadVideoOnYouTube(outputVideoFileLocation, title,
                            finalKeywords, description, location);

                        String videoId = objectYouTubeFunctions.VideoId;
                        // set the object to null to get it garbage collected
                        objectYouTubeFunctions = null;

                        if (statusCode == 2)
                        {
                            // video is successfully uploaded
                            // update the video id and status in table
                            Boolean statusUpdate = objectDatabaseFunctions.updateStatusofYouTubeUploadForAudioFile(
                                audioFileLocation, 2, videoId);

                            // statusUpdate true implies that the upload is successful
                            // dequeue the audio file
                            if (statusUpdate)
                            {
                                lock (locker)
                                {
                                    audioFileLocationQueue.Dequeue();
                                }
                            }
                            // statusUpdate false implies that the database upload failed
                            // Delete the video from YouTube
                            else
                            {
                                objectYouTubeFunctions.deleteVideoFromYouTube();
                            }
                        }
                        else if (statusCode == 3)
                        {
                            // status code 3 implies error in uploading video
                            // make the present audio file last in queue and deal with others

                            // implies tackle other audio
                            if (audioFileLocationQueue.Count > 1)
                            {
                                lock (locker)
                                {
                                    var tempTuple = audioFileLocationQueue.Dequeue();
                                    audioFileLocationQueue.Enqueue(tempTuple);
                                }
                                continue;
                            }
                            // as only one audio is present and that has error in file conversion
                            // lets wait for other audio to come and then will try again 
                            else
                                break;
                        }
                        else if (statusCode == 1)
                        {
                            //most probable reason could be internet connectivity errors
                            // implies try after some time.
                            break;
                        }



                    } // while loop ends. Implies done processing the audio queue for now.
                } // if ends

                EventLog.WriteEntry(sSource, "Consumer task completed processing and is now waiting for more tasks to be assigned", EventLogEntryType.Information);

                waitHandle.WaitOne();

            } // outer infinite while loop ends


        }

    }
}
